<template>
  <div ref="d3demoRef" class="demo-item"></div>
</template>

<script setup lang="ts">
import * as d3 from 'd3'
import { CommonUtil } from 'hippo-module-core/utils'
import { onDeactivated, onMounted, ref } from 'vue'
import { demoData2 } from './data'

const d3demoRef = ref()
const tooltip = ref<any>(null)
const tooltipId = 'd3-tooltip' + CommonUtil.UUID16()

const width = 1000
const height = 900

const marge = { top: 0, bottom: 0, left: 0, right: 0 }

const init = () => {
  d3.select(d3demoRef.value)
    .append('svg')
    .attr('height', height - 10)
    .attr('width', width - 10)

  const svg = d3.select(d3demoRef.value).select('svg')

  svg.attr('width', width)
  svg.attr('height', height)
  const g = svg.append('g').attr('transform', 'translate(' + marge.top + ',' + marge.left + ')')

  const zoom = d3.zoom().on('zoom', (e: any) => {
    g.attr('transform', e.transform)
  })

  svg.call(zoom)

  const forceSimulation = d3
    .forceSimulation(demoData2.nodes)
    .force('collide', d3.forceCollide().radius(99))
    .force('charge', d3.forceManyBody())
    .force('link', d3.forceLink(demoData2.edges))
    .force('center', d3.forceCenter())

  const colorScale = d3
    .scaleOrdinal()
    .domain(d3.range(demoData2.nodes.length))
    .range(d3.schemeCategory10)

  forceSimulation.nodes(demoData2.nodes).on('tick', () => {
    links
      .attr('x1', (d: any) => {
        return d.source.x
      })
      .attr('y1', (d: any) => {
        return d.source.y
      })
      .attr('x2', (d: any) => {
        return d.target.x
      })
      .attr('y2', (d: any) => {
        return d.target.y
      })

    linksText
      .attr('x', (d: any) => {
        return (d.source.x + d.target.x) / 2
      })
      .attr('y', (d: any) => {
        return (d.source.y + d.target.y) / 2
      })

    gs.attr('transform', (d: any) => {
      return 'translate(' + d.x + ',' + d.y + ')'
    })
  })

  forceSimulation
    .force('link')
    .links(demoData2.edges)
    .distance((d: any) => {
      return d.value * 150
    })

  forceSimulation
    .force('center')
    .x(width / 2)
    .y(height / 2)

  const links = g
    .append('g')
    .selectAll('line')
    .data(demoData2.edges)
    .enter()
    .append('line')
    .attr('stroke', (d: any, i: number) => {
      return colorScale(i)
    })
    .attr('stroke-width', 1)
    .attr('stroke-dasharray', '20,5')

  const linksText = g
    .append('g')
    .selectAll('text')
    .data(demoData2.edges)
    .enter()
    .append('text')
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'central')
    .attr('fill', (d: any, i: number) => {
      return colorScale(d.source.index)
    })
    .style('font-size', '12px')
    .style('font-weight', '600')
    .text((d: any, i: number) => {
      return d.relation
    })

  const gs = g
    .selectAll('.circleText')
    .data(demoData2.nodes)
    .enter()
    .append('g')
    .attr('transform', (d: any, i: number) => {
      var cirX = d.x
      var cirY = d.y
      return 'translate(' + cirX + ',' + cirY + ')'
    })
    .call(
      d3
        .drag()
        .on('start', (d: any, i: number) => {
          if (!d.active) {
            forceSimulation.alphaTarget(0.8).restart()
          }
          d.subject.fx = d.subject.x
          d.subject.fy = d.subject.y
        })
        .on('drag', (d: any, i: number) => {
          d.subject.fx = d.x
          d.subject.fy = d.y
        })
        .on('end', (d: any, i: number) => {
          if (!d.active) {
            forceSimulation.alphaTarget(0)
          }
          d.subject.fx = null
          d.subject.fy = null
        })
    )

  gs.append('circle')
    .attr('r', (d: any, i: number) => {
      return d.value
    })
    .attr('fill', (d: any, i: number) => {
      return CommonUtil.hexToRgba(colorScale(d.index), 0.3)
    })

  gs.append('circle')
    .attr('r', (d: any, i: number) => {
      return d.value - 6
    })
    .attr('fill', (d: any, i: number) => {
      return CommonUtil.hexToRgba(colorScale(d.index), 0.6)
    })
    .attr('cursor', 'pointer')
    .on('mouseover', (d: any, data: any) => {
      const ele = d3.select(d.srcElement)
      ele.transition().duration(300).attr('opacity', '0.65')
      tooltipVisible(d, data)
    })
    .on('mouseout', (d: any, data: any) => {
      const ele = d3.select(d.srcElement)
      ele.transition().duration(300).attr('opacity', '1')
      tooltipHidden()
    })

  gs.append('text')
    .attr('text-anchor', 'middle')
    .attr('dominant-baseline', 'central')
    .attr('fill', '#000')
    .style('font-size', '12px')
    .style('font-weight', '600')
    .text((d: any, i: number) => {
      return d.name
    })
    .attr('cursor', 'pointer')
    .on('mouseover', (d: any, data: any) => {
      const ele = d3.select(d.srcElement)
      ele.transition().duration(300).attr('opacity', '0.65')
      tooltipVisible(d, data)
    })
    .on('mouseout', (d: any, data: any) => {
      const ele = d3.select(d.srcElement)
      ele.transition().duration(300).attr('opacity', '1')
      tooltipHidden()
    })
}

const tooltipHidden = () => {
  tooltip.value.transition().duration(300).style('opacity', 0).style('display', 'none')
}

const tooltipVisible = (event: any, data: any) => {
  tooltip.value.transition().duration(300).style('opacity', 1).style('display', 'block')
  tooltip.value
    .html(
      `<div>
         ${data.name}:${data.value}
      </div>`
    )
    .style('left', event.pageX + 2 + 'px')
    .style('top', event.pageY + 2 + 'px')
}

const createTooltip = () => {
  tooltip.value = d3
    .select('body')
    .append('div')
    .attr('id', tooltipId)
    .style('opacity', 0)
    .style('position', 'fixed')
    .style('font-size', '12px')
    .style('text-align', 'center')
    .style('background-color', 'rgba(255,255,255,0.95)')
    .style('border-radius', '3px')
    .style('box-shadow', '0px 0px 5px rgba(10, 10, 10,0.2)')
    .style('padding', '8px')
    .style('z-index', '998')
    .style('cursor', 'pointer')
    .style('display', 'none')
}

const removeTooltip = () => {
  const tooltip = d3.select('body').select('#' + tooltipId)
  if (tooltip) {
    tooltip.remove()
  }
}

onDeactivated(() => {
  removeTooltip()
})

onMounted(() => {
  init()
  createTooltip()
})
</script>

<style lang="scss" scoped>
.demo-item {
  width: 100%;
  height: 100%;
}
</style>
